#ifndef CM_NET_H_
#define CM_NET_H_

#include <arpa/inet.h>
#include <errno.h>
#include <string>
#include <iostream>
#include <sstream>
#include <exception>

using std::string;

#define WAIT_INTERVAL_SEC 10
#define WAIT_INTERVAL_USEC 0
#define MAX_MESSAGE_LENGTH 260
#define PACKET_SIZE 768

enum {
	SERVER_UDP_PORT = 5550, SERVER_TCP_PORT = 5559,
};

struct ConnectionError {
	string what;
	ConnectionError(string w) :
			what(w) {
	}
};

struct GenericSock {
	const int fd;

	~GenericSock();
	void set_timeout(int seconds = WAIT_INTERVAL_SEC, int useconds =
			WAIT_INTERVAL_USEC);
	void bind(const sockaddr_in& servaddr);

	GenericSock(int fd) :
			fd(fd) {
		if (fd < 0)
			throw ConnectionError("No socket :(");
	}
};

template<int STYLE>
struct Sock: GenericSock {
	Sock() :
			GenericSock(::socket(AF_INET, STYLE, 0)) {
	}
	Sock(int fd) :
			GenericSock(fd) {
	}
};

class TcpChannel {
	Sock<SOCK_STREAM> sock;

public:
	TcpChannel(string address, const int port);
	TcpChannel(int sockfd);

	void write_from(std::istream& in);
	void read_into(std::ostream& out);
};

class TcpListener {
	Sock<SOCK_STREAM> sock;

public:
	TcpListener(int port);
	void listen();
	TcpChannel accept();
};

class UdpChannel {
	const sockaddr_in sockaddr;
protected:
	Sock<SOCK_DGRAM> sock;
	void bind();
	UdpChannel(sockaddr_in s);

public:

	UdpChannel* last_addr_p;
	string receive();
	void send(string cmd);

	~UdpChannel() {
		delete last_addr_p;
	}
};

class SafeUdp {
	UdpChannel& ch;
	char xorall(const string& s) {
		char x = 0;
		for (size_t i = 0; i < s.length(); i++) {
			x ^= s[i];
		}
		return x;
	}

public:
	SafeUdp(UdpChannel& ch) : ch(ch) { }

	string receive() {
		string str = ch.receive();
		string actual(str.substr(str.find(" ")+1));
		size_t length;
		std::stringstream ss(str);
		ss >> length;
		int i = 1;
		while (actual.length() < length + 1) {
			string chunk = ch.receive();
			std::cout << "chunk " << i++ << ": " << chunk << "\n";
			actual += chunk;
		}
		char x = actual[actual.length()-1];
		actual.erase(actual.length()-1);
		char sx = xorall(actual);
		if (sx == x)
			return actual;
		throw ConnectionError("string does not make sense");
	}

	void send(const string& str) {
		std::stringstream ss;
		char x = xorall(str);
		ss << str.length() << " " << str << x;
		string to_send = ss.str();
		for (unsigned i = 0; i < to_send.length(); i += PACKET_SIZE) {
			string chunk = to_send.substr(i, PACKET_SIZE);
			ch.send(chunk);
		}
	}
};

struct UdpServer: UdpChannel {
	UdpServer();
};
struct UdpClient: UdpChannel {
	UdpClient(string host_addr);
};

#endif /* CM_NET_H_ */
